import { factory } from "../utils/html";
import mono_hlsb from "../utils/mono_hmsb_in_worker"
import { ComponentClass } from "./ComponentClass";
const canvas = factory('canvas');
const cssPixelated ='image-rendering: optimizeSpeed;' + // FireFox < 6.0
                    'image-rendering: -moz-crisp-edges;' + // FireFox
                    'image-rendering: -o-crisp-edges;' +  // Opera
                    'image-rendering: -webkit-crisp-edges;' + // Chrome
                    'image-rendering: crisp-edges;' + // Chrome
                    'image-rendering: -webkit-optimize-contrast;' + // Safari
                    'image-rendering: pixelated; ' + // Future browsers
                    '-ms-interpolation-mode: nearest-neighbor;'; // IE
export class TileSelector extends ComponentClass{
  /**
   * TileSelector
   * @param {Uint8Array} tileImageData MONO_HLSB tile data (bytes)
   * @param {number} [blockWidth=8] tile cell width
   * @param {number} [blockHeight=8] tile cell height
   * @param {number} [scale=2] scale
   * @param {number} [cols=16] map width
   * @param {[number,number,number]} [color=[0,0,0]] primary color
   * @param {[number,number,number]} [highlightColor=[128,255,0]] highlight color
   * @param {number} [secondTransparent=128] second transparent
   * @param {(index:number, tileMap:TileSelector, event:MouseEvent)=>void} [onSelectTile=undefined] on tile select callback
   */
  constructor(
    tileImageData,
    blockWidth=8,
    blockHeight=8,
    scale=2,
    cols=16,
    color=[0,0,0], //r,g,b
    highlightColor=[128,255,0],
    secondTransparent=128,
    onSelectTile = undefined,
  ){
    super();
    if (blockWidth % 8 !== 0){
      throw new Error("Tile block_width % 8 must be 0!")
    }
    let dataSize = (blockWidth/8) * blockHeight;
    let v_count = Math.ceil(tileImageData.length/dataSize/cols);
    /** @type {HTMLCanvasElement} */
    let cv = canvas({
      width: ((blockWidth*(cols+1)*scale) + (cols+2)), //border 1px, head
      height: ((blockHeight*(v_count+1)*scale) + (v_count+2)), //border 1px, head
      // style: {cssText:cssPixelated}
    });
    /** @type {CanvasRenderingContext2D} */
    let ctx = cv.getContext("2d");
    ctx.imageSmoothingEnabled = false;
    // object
    this.color = color; //rgb color
    this.highlightColor = highlightColor; //hightlight color
    this.secondTransparent = secondTransparent; //grid and axis transparent
    this.scale = scale;
    this.cols = cols;
    this.rows = v_count;
    this.blockWidth = blockWidth;
    this.blockHeight = blockHeight;
    this.blockDataSize = dataSize;
    this.buffer = tileImageData; //mono_hlsb data
    this.element = cv; //canvas element
    this.selectedIndex = -1; //select tile
    this.onSelectTile = onSelectTile; // click callback
    // init
    this.render()
    cv.onclick = this.__handleOnClick.bind(this);
    //test
  }
  valueOf(){
    return this.element;
  }

  // render function below
  async render(){
    // render on off-screen canvas
    /** @type {HTMLCanvasElement} */
    let cv = canvas({
      width: this.element.width,
      height: this.element.height,
    });
    /** @type {CanvasRenderingContext2D} */
    let ctx = cv.getContext("2d", { willReadFrequently: true });
    ctx.imageSmoothingEnabled = false;
    // white background
    ctx.fillStyle = "rgba(255,255,255,0)";
    ctx.fillRect(0,0,this.element.width, this.element.height);
    // draw grid
    this.__renderGrid(ctx);
    // draw axis
    this.__renderAxis(ctx);
    // draw tiles
    let count = Math.floor(this.buffer.length / this.blockDataSize);
    for (let i=0;i<count;i++){
      await this.__drawTile(ctx, i);
    }
    // update on-screen canvas
    this.element.getContext("2d", { willReadFrequently: true }).drawImage(cv,0,0);
  }
  __renderGrid(ctx){
    let data = ctx.getImageData(0,0,this.element.width,this.element.height);
    for (let col=1;col<=this.cols+1;col++){
      let x = (this.blockWidth*this.scale+1) * col;
      for (let y=this.blockHeight*this.scale+1;y<this.element.height;y++){
        let pos = y*this.element.width + x;
        data.data[pos*4] = this.color[0]; //R
        data.data[pos*4+1] = this.color[1]; //G
        data.data[pos*4+2] = this.color[2]; //B
        data.data[pos*4+3] = this.secondTransparent; //A
      }
    }
    for (let row=1;row<=this.rows+1;row++){
      let y = (this.blockHeight*this.scale+1) * row;
      for (let x=this.blockWidth*this.scale+1;x<this.element.width;x++){
        let pos = y*this.element.width + x;
        data.data[pos*4] = this.color[0]; //R
        data.data[pos*4+1] = this.color[1]; //G
        data.data[pos*4+2] = this.color[2]; //B
        data.data[pos*4+3] = this.secondTransparent; //A
      }
    }
    ctx.putImageData(data,0,0);
  }
  __renderAxis(ctx){
    ctx.fillStyle = `rgba(${this.color[0]},${this.color[1]},${this.color[2]},0.5)`;
    ctx.textBaseline = "top";
    ctx.textAlign = "left";
    ctx.font = `${8*this.scale}px monospace`;
    for (let col=1;col<=this.cols;col++){
      let x = (this.blockWidth*this.scale+1) * col;
      // x = x + this.blockWidth*this.scale/4; //padding letf
      ctx.fillText((col-1).toString(16).toUpperCase(),x,1);
    }
    for (let row=1;row<=this.rows;row++){
      let y = (this.blockHeight*this.scale+1) * row;
      ctx.fillText((row-1).toString(16).toUpperCase(),1,y);
    }
  }
  __getLeftUpPixTileRect(index){
    let row = Math.floor(index / this.cols);
    let col = index % this.cols;
    let x = (this.blockWidth*this.scale+1) * (col+1) + 1;
    let y = (this.blockHeight*this.scale+1) * (row+1) + 1;
    let w = this.blockWidth * this.scale;
    let h = this.blockHeight * this.scale;
    return { x, y, w, h };
  }
  __getLeftUpPixBorderRect(index){
    let rect = this.__getLeftUpPixTileRect(index);
    return { x: rect.x-1, y:rect.y-1, w: rect.w+2, h: rect.h+2 };
  }
  async __drawTile(ctx, index, highlight=false){
    let rect = this.__getLeftUpPixTileRect(index);
    let canvasData = ctx.createImageData(rect.w, rect.h);
    if (highlight){
      canvasData.data.set(await mono_hlsb.drawCanvasDataInWorker(this.getTileBytes(index), canvasData.width, canvasData.height, this.scale, this.color, this.highlightColor));
    }else{
      canvasData.data.set(await mono_hlsb.drawCanvasDataInWorker(this.getTileBytes(index), canvasData.width, canvasData.height, this.scale, this.color));
    }
    ctx.putImageData(canvasData, rect.x, rect.y);
  }
  __drawGridBorder(ctx, index, color, transparent){
    let rect = this.__getLeftUpPixBorderRect(index);
    let canvasData = ctx.getImageData(rect.x, rect.y, rect.w, rect.h);
    let size = canvasData.data.length;
    let width = canvasData.width;
    let height = canvasData.height;
    for (let x=0;x<width;x++){
      canvasData.data[x*4] = color[0];
      canvasData.data[x*4+1] = color[1];
      canvasData.data[x*4+2] = color[2];
      canvasData.data[x*4+3] = transparent;
      canvasData.data[size-x*4-4] = color[0];
      canvasData.data[size-x*4-3] = color[1];
      canvasData.data[size-x*4-2] = color[2];
      canvasData.data[size-x*4-1] = transparent;
    }
    for (let y=0;y<height;y++){
      canvasData.data[y*width*4] = color[0];
      canvasData.data[y*width*4+1] = color[1];
      canvasData.data[y*width*4+2] = color[2];
      canvasData.data[y*width*4+3] = transparent;
      canvasData.data[(y*width+width-1)*4] = color[0];
      canvasData.data[(y*width+width-1)*4+1] = color[1];
      canvasData.data[(y*width+width-1)*4+2] = color[2];
      canvasData.data[(y*width+width-1)*4+3] = transparent;
    }
    ctx.putImageData(canvasData, rect.x, rect.y);
  }

  // event handler below
  __handleOnClick(event){
    // convert to canvas axis
    let x = this.element.width*event.offsetX/this.element.clientWidth;
    let y = this.element.height*event.offsetY/this.element.clientHeight;
    let index = this.__getGridIndex(x, y);
    if (index < 0 || this.selectedIndex == index || index >= this.buffer.length/this.blockDataSize){
      return;
    }
    if (typeof(this.onSelectTile) == "function"){
      this.focusOnTile(index);
      this.onSelectTile(index, this, event);
    }
  }
  __getGridIndex(x, y){
    let gridWidth = this.blockWidth*this.scale+1;
    let gridHeight = this.blockHeight*this.scale+1;
    x = x - 1 - gridWidth;
    y = y - 1 - gridHeight;
    let col = Math.floor(x / gridWidth);
    let row = Math.floor(y / gridHeight);
    if (col < 0 || row < 0){
      return -1;
    }
    return row * this.cols + col;
  }

  // public function below
  /** get MONO_HLSB tile data.
   * @param {number} index 
   * @returns {Uint8Array}*/
  getTileBytes(index){
    let start = index*this.blockDataSize
    return this.buffer.slice(start, start+this.blockDataSize);
  }
  /** set MONO_HLSB tile data.
   * @param {number} index
   * @param {Uint8Array} data
   * @param {boolean} [autoUpdate=true] */
  setTileBytes(index, data, autoUpdate=true){
    if (data.length != this.blockDataSize){
      throw new Error(`Data size must be ${this.blockDataSize}`);
    }
    let start = index*this.blockDataSize
    // update buffer
    for (let i=0;i<this.blockDataSize;i++){
      this.buffer[start+i] = data[i];
    }
    // update canvas
    if (autoUpdate){
      if (this.selectedIndex == index){
        this.__drawTile(this.element.getContext("2d", { willReadFrequently: true }), index, true);
      }else{
        this.__drawTile(this.element.getContext("2d", { willReadFrequently: true }), index, false);
      }
    }
  }
  /** get MONO_HLSB data
   * @returns {Uint8Array}
   */
  getTileImageData(){
    return this.buffer;
  }
  /**
   * select tile at index
   * @param {number} index */
  focusOnTile(index){
    this.blurOnTile(); //clear current select
    let ctx = this.element.getContext("2d", { willReadFrequently: true });
    this.__drawGridBorder(ctx, index, this.color, 255);
    this.__drawTile(ctx, index, true);
    this.selectedIndex = index;
  }
  /** cancel select */
  blurOnTile(){
    if (this.selectedIndex >= 0){
      // cancel select
      let ctx = this.element.getContext("2d", { willReadFrequently: true });
      this.__drawGridBorder(ctx, this.selectedIndex, this.color, this.secondTransparent);
      this.__drawTile(ctx, this.selectedIndex, false);
      this.selectedIndex = -1;
    }
  }
  /** current select */
  getFocusTileIndex(){
    return this.selectedIndex;
  }
}